/*
 * Copyright by LunaSec (owned by Refinery Labs, Inc)
 *
 * Licensed under the Business Source License v1.1
 * (the "License"); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 *
 * https://github.com/lunasec-io/lunasec/blob/master/licenses/BSL-LunaTrace.txt
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
import { LunaLogger } from '@lunatrace/logger/build/main';
import { SeverityNamesOsv } from '@lunatrace/lunatrace-common/src/types';

import { hasura } from '../../hasura-api';
import { MaybeError } from '../../types/util';
import { newError, newResult } from '../../utils/errors';
import { notEmpty } from '../../utils/predicates';

import { Manifest, ManifestEdge, ManifestNode } from './types';
import VulnerabilityDependencyTree from './vulnerability-dependency-tree';

export async function loadTree(
  logger: LunaLogger,
  buildId: string,
  minimumSeverity?: SeverityNamesOsv
): Promise<MaybeError<VulnerabilityDependencyTree>> {
  const { builds_by_pk: rawBuildData } = await hasura.GetTreeFromBuild({
    build_id: buildId,
  });

  if (!rawBuildData) {
    return newError('unable to query build data from hasura');
  }

  const rawManifests = rawBuildData.resolved_manifests;

  const ignoredVulnerabilities = rawBuildData.project.ignored_vulnerabilities;

  const uniqueChildIds = new Set<string>();
  rawManifests.forEach((m) => {
    m.child_edges_recursive?.forEach((c) => {
      uniqueChildIds.add(c.child_id);
    });
  });

  const childIdList = [...uniqueChildIds.values()];

  logger.info('collecting child information', {
    childrenCount: childIdList.length,
  });
  // In order to slim down response size, we make this request to populate node data as a separate query, to avoid all the dupes
  const { manifest_dependency_node: childrenInfo } = await hasura.GetManifestDependencyEdgeChildren({
    ids: childIdList,
  });

  const childInfoLookup = new Map<string, ManifestNode>();
  childrenInfo.forEach((c) => {
    childInfoLookup.set(c.id, c);
  });

  const manifests: Array<Manifest> = rawManifests
    .map((m): Manifest => {
      return {
        ...m,
        child_edges_recursive: m.child_edges_recursive
          ?.map((c): ManifestEdge | undefined => {
            const child = childInfoLookup.get(c.child_id);
            if (!child) {
              return undefined;
            }
            return {
              ...c,
              child,
            };
          })
          .filter(notEmpty),
      };
    })
    .filter(notEmpty);

  const folderSettings = rawBuildData.project.project_folder_settings;

  logger.info('building tree', {
    childrenInfoCount: childrenInfo.length,
    manifestCount: manifests.length,
    folderSettingsCount: folderSettings.length,
  });

  if (!manifests || manifests.length === 0) {
    return newError('no manifest data to build tree from');
  }

  const depTree = new VulnerabilityDependencyTree(manifests, folderSettings, ignoredVulnerabilities, minimumSeverity);
  if (!depTree) {
    // tells the client that we didnt get any tree info back and to fall back to grype (for now)
    return newError('unable to build dependency tree');
  }
  return newResult(depTree);
}
